<template>
  <div>
    <div class="topology" id="topologyNode">
      <div class="overflow-hidden absolute w-19/20">
        <div class="service-detail">
          <Collapse v-model:activeKey="activeKey">
            <CollapsePanel key="1" header="服务详情">
              <Descriptions :column="1">
                <DescriptionsItem label="名称">{{ serviceName }}</DescriptionsItem>
                <DescriptionsItem label="类型">{{ serviceType }}</DescriptionsItem>
              </Descriptions>
              <TopoSingleLineChart title="Service ApdexScore" :times="times" :values="serviceApdexScore" />
              <TopoSingleLineChart title="Service SLA" :times="times" :values="serviceSLA" />
            </CollapsePanel>
          </Collapse>
        </div>
        <div class="service-detail-right">
          <Collapse v-model:activeKey="anotherActiveKey">
            <CollapsePanel key="1" header="服务详情">
              <TopoSingleLineChart title="Service ResponseTime" :times="times" :values="serviceResponseTime" />
              <TopoSingleLineChart title="Service Throughput" :times="times" :values="serviceThroughput" />
              <TopoMultiLineChart
                title="百分比响应(服务)"
                :times="times"
                :P50="P50"
                :P75="P75"
                :P90="P90"
                :P95="P95"
                :P99="P99"
              />
            </CollapsePanel>
          </Collapse>
          <Collapse v-model:activeKey="relationActiveKey">
            <CollapsePanel key="1" header="调用关系详情">
              <Descriptions :column="1">
                <DescriptionsItem label="侦察端">{{ detectPointName }}</DescriptionsItem>
              </Descriptions>
              <TopoSingleLineChart title="平均响应时间" :times="callTimes" :values="callResponseTime" />
              <TopoSingleLineChart title="平均吞吐量" :times="callTimes" :values="callThroughput" />
              <TopoSingleLineChart title="平均SLA" :times="callTimes" :values="callSLA" />
              <TopoMultiLineChart
                title="百分比响应(调用关系)"
                :times="callTimes"
                :P50="callP50"
                :P75="callP75"
                :P90="callP90"
                :P95="callP95"
                :P99="callP99"
              />
            </CollapsePanel>
          </Collapse>
        </div>
      </div>
    </div>
  </div>
</template>

<script lang="ts">
import G6 from '@antv/g6';
import { defineComponent, onMounted, ref, watch } from 'vue';
import { Collapse, CollapsePanel, Descriptions, DescriptionsItem } from 'ant-design-vue';
import {
  Call,
  Node,
  QueryServiceTopo,
  QueryTopoCallInfo,
  QueryTopoInfo,
  QueryTopoServiceDetail,
  TopoInfo,
} from '/@/api/maintenance-center/skywalking/topology/model/topologyModel';
import {
  getServicesTopology,
  getServicesTopologyInfo,
  getTopoCallInfo,
  getTopoServiceDetail,
} from '/@/api/maintenance-center/skywalking/topology/topology';
import { DurationDto } from '/@/api/maintenance-center/skywalking/common/model/commonModel';
import TopoSingleLineChart from './TopoSingleLineChart.vue';
import TopoMultiLineChart from './TopoMultiLineChart.vue';

/* eslint-disable */

export default defineComponent({
  components: {
    Collapse,
    CollapsePanel,
    Descriptions,
    DescriptionsItem,
    TopoSingleLineChart,
    TopoMultiLineChart,
  },
  props: {
    start: String,
    end: String,
    step: String,
    serviceIds: Array,
  },
  setup(props) {
    const activeKey = ref([]);
    const anotherActiveKey = ref([]);
    const relationActiveKey = ref([]);

    let nodes: Node[] = [];
    let calls: Call[] = [];
    let topoInfo: TopoInfo;

    let durationDto: DurationDto;

    var graph;

    const serviceName = ref('');
    const serviceType = ref('');

    const showServiceDetail = ref(false);
    const times = ref([]);
    const P50 = ref([]);
    const P75 = ref([]);
    const P90 = ref([]);
    const P95 = ref([]);
    const P99 = ref([]);
    const serviceSLA = ref([]);
    const serviceThroughput = ref([]);
    const serviceResponseTime = ref([]);
    const serviceApdexScore = ref([]);

    const detectPointName = ref('');
    const callTimes = ref([]);
    const callP50 = ref([]);
    const callP75 = ref([]);
    const callP90 = ref([]);
    const callP95 = ref([]);
    const callP99 = ref([]);
    const callSLA = ref([]);
    const callThroughput = ref([]);
    const callResponseTime = ref([]);

    const nodeTypeImgMap = {
      USER: new URL(`../../../../../../assets/images/topology/USER.png`, import.meta.url).href,
      HttpClient: new URL(`../../../../../../assets/images/topology/HTTP.png`, import.meta.url).href,
      SpringMVC: new URL(`../../../../../../assets/images/topology/SPRINGMVC.png`, import.meta.url).href,
      Mysql: new URL(`../../../../../../assets/images/topology/MYSQL.png`, import.meta.url).href,
    };

    // 定义数据源
    const data = {
      // 点集
      nodes: [],
      // 边集
      edges: [],
    };

    // 定义提示框
    const tooltip = new G6.Tooltip({
      offsetX: 15,
      offsety: -20,
      getContent(e) {
        const outDiv = document.createElement('div');
        let edgeId = e.item.getModel().id;
        let cpmList = [...topoInfo.cpmC, ...topoInfo.cpmS];
        let latencyList = [...topoInfo.latencyC, ...topoInfo.latencyS];
        let cpm = cpmList.find((item) => {
          return item.id === edgeId;
        });
        let latency = latencyList.find((item) => {
          return item.id === edgeId;
        });
        outDiv.style.width = 'fit-content';
        outDiv.style.height = '56px';
        outDiv.innerHTML = `
      <ul id="edgeDetails">
        <li>每分钟请求量: ${cpm.value}</li>
        <li>延迟: ${latency.value}</li>
        <li>侦察端: ${e.item.getModel().detectPoints.join(' | ')}</li>
      </ul>`;
        return outDiv;
      },
      itemTypes: ['edge'],
    });

    // 注册自定义节点
    G6.registerNode('topo-node', {
      draw(cfg, group) {
        const opts = cfg;
        const shape = group.addShape('image', {
          name: 'topo-node',
          draggeble: true,
          attrs: {
            img: new URL(`../../../../../../assets/images/topology/CUBE.png`, import.meta.url).href,
            width: 100,
            height: 100,
            opacity: 0.7,
            cursor: 'pointer',
          },
        });

        const localShape = group.addShape('image', {
          attrs: {
            x: 13,
            y: -37,
            height: 65,
            width: 62,
            img: new URL(`../../../../../../assets/images/topology/LOCAL.png`, import.meta.url).href,
            opacity: 0.3,
            cursor: 'pointer',
          },
        });

        if (cfg.label) {
          const style = (cfg.labelCfg && cfg.labelCfg.style) || {};
          style.text = cfg.label;
          const label = group.addShape('text', {
            attrs: style,
            name: 'text-shape',
            draggable: true,
            cursor: 'pointer',
          });
        }

        if (cfg.nodeType && Object.keys(nodeTypeImgMap).indexOf(cfg.nodeType) >= 0) {
          const nodeTypeShape = group.addShape('image', {
            attrs: {
              x: 24,
              y: -32,
              height: 40,
              width: 40,
              img: nodeTypeImgMap[cfg.nodeType],
              opacity: 1,
              cursor: 'pointer',
            },
          });
        }
        return shape;
      },
      setState: (name, value, item) => {
        if (name === 'hover') {
          if (value) {
            const keyShape = item.getKeyShape();
            keyShape.attr('opacity', 1);
          } else {
            const keyShape = item.getKeyShape();
            keyShape.attr('opacity', 0.6);
          }
        }
      },
      update: undefined,
    });

    // 注册自定义边
    const lineDash = [15, 15, 15, 15];
    G6.registerEdge(
      'line-dash',
      {
        afterDraw(cfg, group) {
          const shape = group.get('children')[0];
          let index = 0;
          shape.animate(
            () => {
              index++;
              if (index > 10000) {
                index = 0;
              }
              const res = {
                lineDash,
                lineDashOffset: -index,
              };
              return res;
            },
            {
              repeat: true,
              duration: 7000,
            }
          );
        },
      },
      'quadratic'
    );

    const nodeClickEvent = async (evt) => {
      console.log('节点事件：', evt);
      const item = evt.item;
      let model = item._cfg.model;
      console.log('item._cfg.model: ', model);

      if (model.isReal) {
        serviceName.value = model.label;
        serviceType.value = model.nodeType;

        let queryTopoServiceDetail: QueryTopoServiceDetail = {
          duration: durationDto,
          serviceId: model.id,
        };

        activeKey.value = ['1'];
        anotherActiveKey.value = ['1'];
        relationActiveKey.value = [];

        const topoServiceDetailRes = await getTopoServiceDetail(queryTopoServiceDetail);

        showServiceDetail.value = true;

        times.value = topoServiceDetailRes.times;
        P50.value = topoServiceDetailRes.servicePercentile[0];
        P75.value = topoServiceDetailRes.servicePercentile[1];
        P90.value = topoServiceDetailRes.servicePercentile[2];
        P95.value = topoServiceDetailRes.servicePercentile[3];
        P99.value = topoServiceDetailRes.servicePercentile[4];
        serviceSLA.value = topoServiceDetailRes.serviceSLA.map((item) => {
          return item / 100;
        });
        serviceThroughput.value = topoServiceDetailRes.serviceThroughput;
        serviceResponseTime.value = topoServiceDetailRes.serviceResponseTime;
        serviceApdexScore.value = topoServiceDetailRes.serviceApdexScore.map((item) => {
          return item / 10000;
        });
      }
    };

    const edgeClickEvent = async (evt) => {
      console.log('边事件：', evt);
      const item = evt.item;
      let model = item._cfg.model;
      console.log('item._cfg.model: ', model);

      detectPointName.value = model.detectPoints.join(' & ');

      let queryTopoCallInfo: QueryTopoCallInfo = {
        duration: durationDto,
        id: model.id,
        detectPoint: model.detectPoints[0],
      };

      activeKey.value = [];
      anotherActiveKey.value = [];
      relationActiveKey.value = ['1'];

      const topoCallInfo = await getTopoCallInfo(queryTopoCallInfo);

      callTimes.value = topoCallInfo.times;
      callP50.value = topoCallInfo.percentile['P50'];
      callP75.value = topoCallInfo.percentile['P75'];
      callP90.value = topoCallInfo.percentile['P90'];
      callP95.value = topoCallInfo.percentile['P95'];
      callP99.value = topoCallInfo.percentile['P99'];
      callSLA.value = topoCallInfo.sla.map((item) => {
        return item / 100;
      });
      callThroughput.value = topoCallInfo.throughput;
      callResponseTime.value = topoCallInfo.responseTime;
    };

    watch(
      () => [props.start, props.end, props.step, props.serviceIds],
      async (val, prevVal) => {
        console.log('Topology 子组件监听父组件值变化', val);
        let serviceIdList = val[3];
        let isServiceIdsUndefined = serviceIdList?.length === 0 || serviceIdList == null || serviceIdList == undefined;

        if (!isServiceIdsUndefined && !ifDateUndefined(val[0], val[1], val[2])) {
          durationDto = {
            start: val[0],
            end: val[1],
            step: val[2],
          };

          let queryServiceTopo: QueryServiceTopo = {
            duration: durationDto,
            serviceCodes: serviceIdList,
          };

          const res = await getServicesTopology(queryServiceTopo);

          nodes = res.nodes;
          calls = res.calls;

          let ids = nodes.map((node) => node.id);

          let idsC = [];
          let idsS = [];

          for (let i = 0; i < calls.length; i++) {
            if (calls[i].detectPoints.includes('SERVER')) {
              idsS.push(calls[i].id);
            }

            if (calls[i].detectPoints.includes('CLIENT')) {
              idsC.push(calls[i].id);
            }
          }

          let queryTopoInfoParam: QueryTopoInfo = {
            duration: durationDto,
            ids: ids,
            idsS: idsS,
            idsC: idsC,
          };

          topoInfo = await getServicesTopologyInfo(queryTopoInfoParam);

          console.log('topoInfoRes: ', topoInfo);

          data.nodes = nodes.map((node) => ({
            id: node.id,
            label: node.name,
            nodeType: node.type,
            isReal: node.isReal,
            type: 'topo-node',
            style: {
              cursor: 'pointer',
            },
            labelCfg: {
              position: 'bottom',
              style: {
                // x: 0, // 居中
                y: 120,
                fontWeight: 300,
                fontFamily: 'Verdana',
                fontSize: 20,
                textAlign: 'left',
                textBaseline: 'middle',
                fill: '#3058CF',
                stroke: '#3058CF',
                lineWidth: 1,
              },
            },
          }));

          data.edges = calls.map((edge) => ({
            id: edge.id,
            detectPoints: edge.detectPoints,
            source: edge.source,
            target: edge.target,
          }));

          // 更新数据源
          graph.changeData();
          graph.render();
        }
      },
      { immediate: true, deep: true }
    );

    function ifDateUndefined(start: string, end: string, step: string) {
      let isStartUndefined = start == '' || start == null || start == undefined;
      let isEndUndefined = end == '' || end == null || end == undefined;
      let isStepUndefined = step == '' || step == null || step == undefined;
      if (isStartUndefined || isEndUndefined || isStepUndefined) {
        return true;
      }
      return false;
    }

    onMounted(() => {
      // 创建 G6 图实例
      graph = new G6.Graph({
        container: 'topologyNode', // 指定图画布的容器 id，与第 9 行的容器对应
        // 画布宽高
        fitCenter: true,
        width: 1700,
        height: 760,
        plugins: [tooltip],
        fitView: true, // 是否开启画布自适应。开启后图自动适配画布大小。
        fitViewPadding: 100, // 画布的padding值
        layout: {
          type: 'random',
          width: 1700,
          height: 760,
        },
        modes: {
          default: ['drag-canvas', 'zoom-canvas', 'drag-node'], // 允许拖拽画布、放缩画布、拖拽节点、设置高亮
        },
        defaultNode: {
          type: 'topo-node',
          style: {
            cursor: 'pointer',
          },
        },
        defaultEdge: {
          type: 'line-dash',
          style: {
            lineWidth: 2,
            stroke: '#AFB4B6',
            cursor: 'pointer',
          },
        },
      });

      // 读取数据
      graph.data(data);

      // 绑定节点点击事件
      graph.on('node:click', nodeClickEvent);

      graph.on('node:mouseenter', (e) => {
        graph.setItemState(e.item, 'hover', true);
      });

      graph.on('node:mouseleave', (e) => {
        graph.setItemState(e.item, 'hover', false);
      });

      // 绑定边点击事件
      graph.on('edge:click', edgeClickEvent);

      graph.on('edge:mouseenter', (e) => {
        graph.setItemState(e.item, 'active', true);
      });

      graph.on('edge:mouseleave', (e) => {
        graph.setItemState(e.item, 'active', false);
      });

      // 渲染图
      graph.render();
    });

    return {
      props,
      activeKey,
      anotherActiveKey,
      relationActiveKey,
      showServiceDetail,
      serviceName,
      serviceType,
      times,
      P50,
      P75,
      P90,
      P95,
      P99,
      serviceSLA,
      serviceThroughput,
      serviceResponseTime,
      serviceApdexScore,
      detectPointName,
      callTimes,
      callP50,
      callP75,
      callP90,
      callP95,
      callP99,
      callSLA,
      callThroughput,
      callResponseTime,
    };
  },
});
</script>

<style lang="less" scoped>
.topology {
  // background: #e6ecff;
  background: #e6ecff;
}

.service-detail {
  width: 400px;
  // position: sticky;
  float: left;
  // bottom: 550px;
  // left: 20px;
  // background: red;
}

.service-detail::-webkit-scrollbar {
  display: none; /* Chrome Safari */
}

.service-detail-right {
  width: 400px;
  // position: sticky;
  // bottom: 800px;
  // right: 20px;
  // position: relative;
  // right: 20px;
  float: right;
  // height: 740px;
  // overflow-y: scroll;
  // scrollbar-width: none;
  // -ms-overflow-style: none; /* IE 10+ */
}

.service-detail-right::-webkit-scrollbar {
  display: none; /* Chrome Safari */
}
</style>
